"""

.. module:: dev_autoload_service
.. moduleauthor:: @author: 

*Description*:

This module contains the DevAutoloadService class.

*Constants*:

.. data:: ACTIONS
        
    ACTIONS is a dictionary of ...
        
.. data:: FILE_LIST_DIRECTORY
        
    FILE_LIST_DIRECTORY is ...

.. seealso:: class :class:`runtime.service.Service`
"""

# Imports
#
import os
import sys
import time
import hashlib

if sys.platform == 'win32':
    import win32file
    import win32con
elif sys.platform == 'darwin': # OS/X
    pass
else: # assume Linux
    pass

from datastore.database import NotFoundException
from datastore.resource import *
from datastore.uri import *
from runtime.service import *

# Constants
#
ACTIONS = {
      1 : "Created",
      2 : "Deleted",
      3 : "Updated",
      4 : "Renamed from something",
      5 : "Renamed to something"
    }
FILE_LIST_DIRECTORY = 0x0001

# Classes
#

class DevAutoloadService(Service):
    """
    **Constants**:
    """
    
    # TODO: Change logic to do a HTTP PUT on some other node.
    # TODO: Change to make some changes dynamic. Perhaps only code changes
    # need to do a full restart.
    
    def cb_start(self, uri, action, res):
        """
        :param uri: Uri
        :param action:
        :param res: Resource
        """
        
        # res has also been saved as self.service_def
        self.logger.log(logging.DEBUG, "Autoload service uri %s", uri) 
        res1 = Resource(type='type.t.b.a')
        res1.project = 'base'
        self.filewatch = self.async(self.cb_autoload, uri, action, res1,
                                    name='Autoload:base')
#        res2 = Resource(type='type.t.b.a')
#        res2.project = 'eplanning'
#        self.filewatch = self.async(self.cb_autoload, uri, action, res2,
#                                    name='Autoload:eplanning')

    def cb_autoload(self, uri, action, res):
        """
        :param uri: Uri
        :param action:
        :param res: Resource
        """
        
        project = res.project
        self.logger.log(logging.DEBUG, "Autoload started uri (%s) for %s.", 
                        sys.platform, project)
        root = self.spaces.get(Uri('internal:base/root'))
        path_to_watch = root.extra['watch-node'] # set in build.xml
        if sys.platform == 'win32':
            self.win_filewatch(path_to_watch, project) 
        elif sys.platform == 'darwin': # OS/X
            pass
        else: # assume Linux
            pass

    def update_datastore(self, filename, action, project):
        """
        :param filename:
        :param action:
        :param project:
        """
        
        #self.logger.log(logging.DEBUG, "Update file (%s).", filename) 
        
        path_name, file_name = os.path.split(filename)
        p = path_name.replace('\\', '/')
        package_bit = p[p.find('/src/')+5:] 
        package_name = package_bit.replace('/', '.')
        result = self.process_file(filename, package_name, project)
        if type(result) in [types.StringType, types.UnicodeType]:
            self.logger.log(logging.DEBUG, result)
        else:
            #self.logger.log(logging.DEBUG, "aaaa (%s)", str(result))
            result.set_metadata(Uri('term:base/build/revision'), 'autoload')
            # get the existing resource
            meta1 = Uri('term:base/build/autoload_suffix')
            meta2 = Uri('term:base/datastore/resource/type')
            try:
                existing = self.datastore.find([
                    (meta1, result.get_metadata(meta1)),
                    (meta2, result.get_metadata(meta2))])
                if len(existing) == 1:
                    id = existing[0].get_id()
                    # save the new resource (override by grabbing its id).
                    result.set_id(id)
            except NotFoundException:
                # this must be a new file so don't bother with the id.
                pass
            self.datastore.put(result)
            self.logger.log(logging.DEBUG, "Replacing file (%s)", filename)
            
            # and restart the node
            control_res = Resource(type='term:base/runtime/node_control')
            control_res.action = 'restart'
            self.spaces.put(Uri('internal:base/node_control'), control_res)

# Note: If updating the code, tab code and add 'self' to the action signature.
    #-------------------------------------------------------------------------
    # this is copied as is to the dev_autoload_service module
    def process_file(self, file_path_name, package_name, project):
        path_name, file_name = os.path.split(file_path_name)
        
        if (os.path.isdir(file_path_name)):
            return "Ignoring directory (%s)" % file_path_name
        
        for bit in path_name.split(os.sep):
            if bit.startswith('.'):
                return "Ignoring path (bit with a period) (%s)" % file_path_name
    
        if file_name.startswith('.'):
            return "Ignoring file (starts with a period) (%s)" % file_path_name
    
        if file_name.endswith('.pyc'):
            return "Ignoring compiled python file (%s)" % file_path_name
    
        if file_name.endswith('test.py'):
            return "Ignoring python unittest file (%s)" % file_path_name
    
        #print "Adding file (%s)" % file_path_name
        file = None
        try:
            # Note that the parser only accepts the Unix-style end of line 
            # convention. If you are reading the code from a file, make sure 
            # to use universal newline mode to convert Windows or Mac-style 
            # newlines.
            # http://docs.python.org/reference/simple_stmts.html#exec
            if file_path_name.lower().endswith('.jpg'):
                print("  Opened (%s) as binary." % file_path_name)
                file = open(file_path_name, 'rb')
            else:
                file = open(file_path_name, 'rU')
            content = file.read()
        except:
            exctype, value = sys.exc_info()[:2]
            return "Error (%s) opening (%s)." % (value, file_path_name)
        if file is not None:
            file.close()
        file = None
    
        res = None
    
        if (file_name.endswith('.xml')) or (file_name.endswith('.html')):
            try:
                res = Resource(content)
            except DeserialisationException as d:
                return "File (%s) is not a valid deserialised resource (%s)" % \
                    (file_path_name, d)
            resource_type = res.get_type()
        else:
            if (not content) or (not content.strip()):
                return "Cannot add empty file (%s)" % file_path_name
            
            # generate a resource from a file, starting with an empty Resource
            res = Resource(empty=True)
            module_pathname = ""
            if file_name == '__init__.py':
                module_pathname = package_name
            else:
                # strip the characters after, and including, "."
                dotPos = file_name.find(".")
                if (dotPos > -1):
                    module_pathname = ''.join([package_name, ".", 
                                               file_name[0:dotPos]])
                else:
                    module_pathname = ''.join([package_name, ".", file_name])
                #module_pathname = file_name.split('.')[0]
            res.set_metadata(Uri('term:base/build/package_module'), module_pathname)
            res.set_metadata(Uri('term:base/build/file'), file_name)
            #res.set_metadata(Uri('term:base/build/package'), package_name)
            res.set_metadata(Uri('term:base/build/project'), project)
            #print("  f:%s p:%s m:%s proj:%s" % \
            #      (file_name, package_name, module_pathname, project))
            
            res.content_length =  len(content)
            res.content_md5_digest = hashlib.md5(content).hexdigest()
            #print("  Digest (%s)" % hashlib.md5(content).hexdigest())
            res.content = Data(content)   # base64 encode data
            
            resource_type = None
            
            if (file_name.endswith('.js')):
                resource_type = Uri('term:base/runtime/file')
                res.set_metadata(Uri('term:base/runtime/file/file_uri'), 
                                Uri('external:js/') + project + file_name)
                res.content_mime_type = 'application/javascript'
            elif (file_name.endswith('.css')):
                resource_type = Uri('term:base/runtime/file')
                res.set_metadata(Uri('term:base/runtime/file/file_uri'), 
                                Uri('external:css/') + project + file_name)
                res.content_mime_type = 'text/css'
            elif (file_name.endswith('.jpg')):
                resource_type = Uri('term:base/runtime/file')
                res.set_metadata(Uri('term:base/runtime/file/file_uri'), 
                                Uri('external:images/') + project + file_name)
                res.content_mime_type = 'image/jpeg'
            elif (file_name.endswith('.gif')):
                resource_type = Uri('term:base/runtime/file')
                res.set_metadata(Uri('term:base/runtime/file/file_uri'), 
                                Uri('external:images/') + project + file_name)
                res.content_mime_type = 'image/gif'
            elif (file_name.endswith('.png')):
                resource_type = Uri('term:base/runtime/file')
                res.set_metadata(Uri('term:base/runtime/file/file_uri'), 
                                Uri('external:images/') + project + file_name)
                res.content_mime_type = 'image/png'
            elif (file_name.endswith('.ico')):
                resource_type = Uri('term:base/runtime/file')
                res.set_metadata(Uri('term:base/runtime/file/file_uri'), 
                                Uri('external:images/') + project + file_name)
                res.content_mime_type = 'image/icon'
            elif (file_name.endswith('.py')):
                resource_type = Uri('term:base/runtime/module')
            else:
                return "Cannot add unknown file type (%s)" % file_path_name
        
        # add the suffix to every file (including XML)
        # unless its already been specified.
        package = package_name.replace('.', '/')
        if not res.metadata_has_key(Uri('term:base/build/autoload_suffix')):
            res.set_metadata(Uri('term:base/build/autoload_suffix'),
                    "%s/%s/%s" % (project, package, file_name.split('.')[0]))
            
        res.set_type(resource_type)
        #print("  as type %s" % resource_type)
        return res
    #-------------------------------------------------------------------------
    
    def win_filewatch(self, path_to_watch, project):
        """
        ref http://timgolden.me.uk/
                   python/win32_how_do_i/watch_directory_for_changes.html
        
        :param path_to_watch:
        :param project:
        """
        
        hDir = win32file.CreateFile(
            path_to_watch,
            FILE_LIST_DIRECTORY,
            win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
            None,
            win32con.OPEN_EXISTING,
            win32con.FILE_FLAG_BACKUP_SEMANTICS,
            None
            )
        
        # remove duplicate entries
        saved_time = time.time()
        saved_file = None
        saved_action = None
        while True:
            results = win32file.ReadDirectoryChangesW(
            hDir,
            8192, 
            True,
            win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
                win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
                win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
                win32con.FILE_NOTIFY_CHANGE_SIZE |
                win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
                win32con.FILE_NOTIFY_CHANGE_SECURITY,
            None,
            None
            )
            for action, file in results:
                full_filename = os.path.join(path_to_watch, file)
                now = time.time()
                if (saved_file == full_filename) and (saved_action == action):
                    if (now - saved_time) < 0.1:
                        saved_time = now
                        continue
                saved_time = now
                saved_file = full_filename
                saved_action = action
                self.update_datastore(full_filename, 
                                      ACTIONS.get(action, "Unknown"),
                                      project)
